#!/usr/bin/env python
# -*- coding: utf-8 -*-
#===============================================================================
# Copyright 2011 zod.yslin
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 
# Author: zod.yslin
# Email: 
# File Name: mm.py
# Description: 
#   mm module is for Multimethods in argument.py
# Edit History: 
#   2011-09-15    File created.
#===============================================================================
registry = {}

class MultiMethod(object):
    """
    The rest of this article will show how we can define the multimethod decorator.
    It's really pretty simple: there's a global registry indexed by function name ('foo' in this case),
    pointing to a registry indexed by tuples of type objects corresponding to the arguments passed to the decorator.
    """
    def __init__(self, name):
        """
        The __init__() constructor creates a MultiMethod object with a given name and an empty typemap.
        (The name argument is only used for diagnostics; I'm leaving these out for brevity.)
        """
        self.name = name
        self.typemap = {}
    def __call__(self, *args):
        """
        The __call__() method makes the MultiMethod object itself callable.
        It looks up the actual function to call in the typemap based on the types of the arguments passed in;
        if there's no corresponding entry it raises TypeError.
        The biggest simplification here is that we don't try to find an inexact match using subclass relationships;
        a real multimethod implementation should attempt to find the best match such that the actual argument types are subclasses of the types found in the typemap. This is left as an exercise for the reader (or maybe I'll blog about it next week,
        if there's demand; there are a few subtleties when there's more than one near miss).
        """
        types = tuple(arg.__class__ for arg in args) # a generator expression!
        function = self.typemap.get(types)
        if function is None:
            raise TypeError("no match")
        return function(*args)
    def register(self, types, function):
        """
        The register() method adds a new function to the typemap; it should be called with a tuple of type objects (== classes) and a function.
        """
        if types in self.typemap:
            raise TypeError("duplicate registration")
        self.typemap[types] = function

def multimethod(*types):
    """
    I hope it's clear from this that the @multimethod decorator should return a MultiMethod object and somehow call its register() method.
    """
    def register(function):
        name = function.__name__
        print(name, function)
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        return mm
    return register

def multimethod2(*types):
    """
    I've got one improvement to make: I imagine that somtimes you'd want to write a single implementation that applies to multiple types.
    It would be convenient if the @multimethod decorators could be stacked
    This can be done by changing the decorator slightly (this is not thread-safe,
    but I don't think that matters much, since all this is typically happening at import time):
    """
    def register(function):
        """
        Note the three-argument getattr() call, which you may not be familiar with: 
            getattr(x, "y", z) returns x.y if it exists, and z otherwise. So that line is equivalent to

        if hasattr(function, "__lastreg__"):
                function = function.__lastreg__
        """
        function = getattr(function, "__lastreg__", function) 
        name = function.__name__
        mm = registry.get(name)
        if mm is None:
            mm = registry[name] = MultiMethod(name)
        mm.register(types, function)
        mm.__lastreg__ = function
        return mm
    return register
